現在,我們將進入到 Grafana 的告警系統的 IaC 實作部分。這部份是 Grafana 中相對複雜的部份,需要對告警系統有一定的了解,才能夠更好地進行實作。 並且一直都還沒有人對這部份的資源做過 IaC 的實作(如果有的話,請告訴我讓我參考!),因為 Alerting 也是 Grafana 所有資源中最後期才加入當中的,並且自成體系相當複雜。在過程中,也遇到不少需要取捨以及現實如何將在 UI 上操作的習慣,轉換成能夠完全 IaC 的設計,並且要兼具高效以及操作的彈性,否則就失去了 IaC 的意義了。
Grafana Alerting 的資源相當多,我們必須對於所有的告警相關資源有所了解,才能夠更好地進行實作:
Grafana Alerting Resource | Terraform Grafana Provider Resource |
---|---|
Alert rules | grafana_rule_group |
Contact points | grafana_contact_point |
Notification templates | grafana_message_template |
Notification policy tree | grafana_notification_policy |
Mute timings | grafana_mute_timing |
在瞭解了所列出的資源後,我們可以粗略的畫出所有資源之間的關係圖:
從中我們可以知道,其實所有的資源都是環環相扣。更可以說所有資源都圍繞著 Alert Rule 和 Notification Policy 為中心搭建而成。
Folder 是用來組織和管理告警規則組的容器。一個 folder 可以包含多個 rule_group,這種結構使得告警規則的管理變得更加有條理和系統化。通過使用 folder,管理員可以更容易地組織、查找和維護大量的告警規則。
Rule Group 是一組相關告警規則的集合。每個 rule_group 可以包含多個 rule,並且為這些規則設置統一的評估頻率。這種分組方式使得管理員可以更有效地管理和監控相似類型或相關聯的告警規則。
Rule 定義了具體的告警條件和邏輯。它可以從一個或多個 datasource 查詢資料,根據預設的條件判斷是否觸發告警。同時,rule 可以與特定的儀表板(dashboard)和面板(panel)關聯,使得告警資訊可以直觀地在 Grafana 界面上呈現。
Rule 定義了告警的具體條件和觸發邏輯,在發送告警通知時提供了靈活的路由選擇。當告警被觸發時,系統可以採用兩種方式之一來發送通知。一種是直接將告警發送到與 Rule 關聯的特定 Contact Point,這種方式簡單直接,適用於固定的通知需求。另一種是通過 Notification Policy 進行路由,系統會根據 Rule 的標籤(labels)匹配 Notification Policy 中定義的規則,然後決定使用哪個 Contact Point 來發送通知。這種方法提供了更複雜和動態的路由邏輯,能夠根據不同的情況靈活地選擇通知方式。管理員可以根據具體需求和場景,選擇最適合的通知路由方式,從而實現高效且有針對性的告警管理。
Notification Policy 定義了告警通知的整體策略。它可以使用多個 contact point 來指定不同的通知方式和接收者,匹配多個 rule 來決定哪些告警需要通知,並配置多個 mute timing 來設置靜音時間段。這種靈活的策略配置使得告警通知可以更精確地達到目標接收者。
Contact Point 定義了告警通知的具體接收方式和接收者。每個 contact point 可以使用多個 message template 來自定義通知的格式和內容。這種設計使得告警通知可以根據不同的場景和需求,以最適合的方式呈現給接收者。
這些資源和它們之間的關係使得我們可以靈活地配置和管理 Grafana 中的告警系統,實現高效的告警通知和管理。
讓我們深入探討如何使用 Terraform 來管理這些資源。我們將從設計全局資源結構開始,然後逐步實現各個組件。
locals {
alerting = {
rule_groups = {
"Trading Service - 1m" = {
folder_name = "Engineering"
interval_seconds = 60
disable_provenance = true # 允許在 UI 上修改
rules = {
TradingError = {
condition = "TradingErrorThreshold"
no_data_state = "NoData"
exec_err_state = "Error"
for = "5m"
notification_settings = {
contact_point = "My contact point email"
group_by = []
mute_timings = ["No weekends"]
}
annotations = {
"Custom annotation name" = "Custom annotation context"
__dashboardUid__ = "advgkpnyhc5j4a"
__panelId__ = "11"
description = "Add Description"
runbook_url = "https://www.google.com"
summary = "Add Summary"
}
labels = {
label_key = "label_value"
}
is_paused = false
data_file = "${path.module}/rules_data/trading_error.json"
disable_provenance = false # 允許在 UI 上修改
},
# 可以在這裡添加更多規則
}
}
}
contact_points = {
"My contact point email" = {
email = ["alice@example.com"]
message = "{{ template \"custom_email.message\" .}}"
disable_provenance = true # 不允許在 UI 上修改
}
}
message_templates = {
"Custom email message" = {
template = <<-EOT
{{ define "custom_email.message" }}
Lorem ipsum - Custom alert!
{{ end }}
EOT
disable_provenance = true # 不允許在 UI 上修改
}
}
mute_timings = {
"No weekends" = {
intervals = { weekdays = ["saturday", "sunday"] }
disable_provenance = false # 允許在 UI 上修改
}
}
}
}
在上述的 Grafana 告警設定中,我們採用了一個簡潔且靈活的頂層架構設計。特別值得注意的是,我們將 Alert Rule 的詳細設定(data)單獨拆分出來,存儲在獨立的 JSON 文件中。這種設計方法有以下幾個優點:
這種方法類似於我們處理 Grafana dashboard JSON 的方式。對於像告警規則這樣可能包含大量細節且不固定的配置,很難設計出一個完全制式和動態的模板。因此,直接使用文件導入的形式是一個實用的解決方案。
Note: 我們想要進一步的模組化管理 JSON 檔內容的話,可以透過我們先前提過 Jsonnet 的語法來進行管理,進一步提升 JSON 的彈性和可維護性。
data "grafana_folder" "folders" {
for_each = { for k, v in local.alerting.rule_groups : k => v.folder_name }
title = each.value
}
resource "grafana_rule_group" "rule_groups" {
for_each = local.alerting.rule_groups
name = each.key
folder_uid = data.grafana_folder.folders[each.key].uid
interval_seconds = each.value.interval_seconds
disable_provenance = each.value.disable_provenance
dynamic "rule" {
for_each = each.value.rules
content {
name = rule.key
condition = rule.value.condition
no_data_state = rule.value.no_data_state
exec_err_state = rule.value.exec_err_state
for = rule.value.for
annotations = rule.value.annotations
labels = rule.value.labels
is_paused = rule.value.is_paused
dynamic "notification_settings" {
for_each = rule.value.notification_settings != null ? [rule.value.notification_settings] : []
content {
contact_point = notification_settings.value.contact_point
group_by = notification_settings.value.group_by
mute_timings = notification_settings.value.mute_timings
}
}
dynamic "data" {
for_each = jsondecode(file(rule.value.data_file))
content {
ref_id = data.value.refId
relative_time_range {
from = data.value.relativeTimeRange.from
to = data.value.relativeTimeRange.to
}
datasource_uid = data.value.datasourceUid
model = jsonencode(data.value.model)
}
}
}
}
}
在這裡我們定義了 Rule Group 的資源,並且透過先前介紹的 dynamic block 來動態生成多個 Rule Group。
並且我們還有另一個小巧思在於,我們在 local 中指定的不是 folder_uid,而是 folder_name,並且在 data source 的部份,我們使用 data "grafana_folder" "folders"
的方式來動態的取得 folder_uid。這樣的設計將更符合我們在 UI 上的操作習慣,也讓我們在管理多個告警規則時更加方便。
resource "grafana_contact_point" "contact_points" {
for_each = local.alerting.contact_points
name = each.key
disable_provenance = each.value.disable_provenance
email {
addresses = each.value.email
message = each.value.message
}
}
resource "grafana_message_template" "message_templates" {
for_each = local.alerting.message_templates
name = each.key
template = each.value.template
disable_provenance = each.value.disable_provenance
}
resource "grafana_mute_timing" "mute_timings" {
for_each = local.alerting.mute_timings
name = each.key
disable_provenance = each.value.disable_provenance
intervals {
weekdays = each.value.intervals.weekdays
}
}
在這裡我們定義了 Contact Point 、Message Template 和 Mute Timing 的資源,複雜度比起 Rule Group 來說簡單許多。
terraform init
terraform apply
---
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# grafana_contact_point.contact_points["My contact point email"] will be created
+ resource "grafana_contact_point" "contact_points" {
+ disable_provenance = true
+ id = (known after apply)
+ name = "My contact point email"
+ email {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
}
# grafana_message_template.message_templates["Custom email message"] will be created
+ resource "grafana_message_template" "message_templates" {
+ disable_provenance = true
+ id = (known after apply)
+ name = "Custom email message"
+ template = <<-EOT
{{ define "custom_email.message" }}
Lorem ipsum - Custom alert!
{{ end }}
EOT
}
# grafana_mute_timing.mute_timings["No weekends"] will be created
+ resource "grafana_mute_timing" "mute_timings" {
+ disable_provenance = false
+ id = (known after apply)
+ name = "No weekends"
+ intervals {
+ weekdays = [
+ "saturday",
+ "sunday",
]
}
}
# grafana_rule_group.rule_groups["Trading Service - 1m"] will be created
+ resource "grafana_rule_group" "rule_groups" {
+ disable_provenance = true
+ folder_uid = "engineering"
+ id = (known after apply)
+ interval_seconds = 60
+ name = "Trading Service - 1m"
+ rule {
+ annotations = {
+ "Custom annotation name" = "Custom annotation context"
+ "__dashboardUid__" = "advgkpnyhc5j4a"
+ "__panelId__" = "11"
+ "description" = "Add Description"
+ "runbook_url" = "https://www.google.com"
+ "summary" = "Add Summary"
}
+ condition = "TradingErrorThreshold"
+ exec_err_state = "Error"
+ for = "5m"
+ is_paused = false
+ labels = {
+ "label_key" = "label_value"
}
+ name = "TradingError"
+ no_data_state = "NoData"
+ uid = (known after apply)
+ data {
// ...
}
+ data {
// ...
}
+ data {
// ...
}
+ notification_settings {
+ contact_point = "My contact point email"
+ group_by = []
+ mute_timings = [
+ "No weekends",
]
}
}
}
Plan: 4 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
接著我們可以使用 Grafana 查看我們剛剛建立的資源:
到此,我們已經完成了幾乎 Grafana 上所有資源的 IaC 實作,包含 Dashboard、Alerting、Folder 和 Datasource。這也意味著我們已經完成了大部份在 Grafana 上的操作都可以轉換成 Terraform 的語法來進行實作。而詳細在團隊中如何實作 CI/CD 的流程,以及如何讓工程師們能夠順利地將 IaC 佈署在不同的環境中,就留待各位看官們自行發揮想像了。礙於篇幅,我們就不在這系列文中進行深入探討。
總之,透過使用 Terraform 來管理 Grafana 的 Alerting 資源,我們可以實現一個高度可配置、版本控制的告警系統。這種方法不僅提高了管理效率,還確保了跨環境的一致性和可重複性。期待能帶給各位讀者一些啟發,讓大家都能夠在各自的實務上,將 IaC 的思維應用到更多的地方。
References: